package cz.acies.uniql.metadata;

import java.io.Serializable;
import java.sql.*;
//import java.util.ArrayList;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.dom.DOMElement;

import cz.acies.uniql.dialect.*;

/**
 * The Class FieldMetaData.
 * 
 * T��da je kontejnerem pro MetaData sloupce konkr�tn� tabulky.
 * 
 */
public class FieldMetaData implements Serializable, Cloneable
{
	private static final long serialVersionUID = -6230117160568465688L;
	
	/*
	public static final int DEFAULT_LENGTH = 255;
	public static final int DEFAULT_PRECISION = 19;
	public static final int DEFAULT_SCALE = 2;

	private int length = DEFAULT_LENGTH;
	private int precision = DEFAULT_PRECISION;
	private int scale = DEFAULT_SCALE;
	*/
	private String table = null;
	private String name = null;
	private String type = null;
	private String value = null;
	private String defaultValue = null;
	private int sqltype;
	private int size;
	private int decimal;
	
	private boolean nullable = true;
	private boolean prefix = false;
	private boolean pk=false;
	private boolean unique=false;
	private boolean autoincrement = false;
	
	private Dialect dialect = null;
	private DBMetaData dbmd = null;
	private TableMetaData tbmd = null;
	
	private boolean quoted = true;
	
	// -----  -----------------------------------------------------------------

	/**
	 * Konstruktor FieldMetaData.
	 */
	public FieldMetaData() {}
	
	public FieldMetaData(Dialect dialect, DBMetaData dbmd) {
		this.dialect = dialect;
		this.dbmd = dbmd;
	}

	/**
	 * Na�te z ResultSetu rs MetaData tabulky.
	 * 
	 * @param table - jmeno tabulky
	 * @param rs - ResultSet MetaDat tabulky ziskan� pomoc� funkce z getColumns
	 * cz.acies.uniql.connect.ConnectHelper  
	 * 
	 * @throws Exception
	 */
	public void parse(String table, ResultSet rs) throws Exception {
		try {
			//System.out.println("FieldMetaData.parse(): " + table + ", " + rs.getString("COLUMN_NAME"));
			setName(rs.getString("COLUMN_NAME"));
			setTable(table);
			setSize(rs.getInt("COLUMN_SIZE"));
			setDecimal(rs.getInt("DECIMAL_DIGITS"));
			setTypeName(rs.getString("TYPE_NAME"));
			setSqlType(rs.getInt("DATA_TYPE"));
			String nullable = rs.getString("IS_NULLABLE");
			setNullable((nullable.trim().equals("NO")) ? false : true);
			
			String defa = "";
			try { 
				defa = dialect.prepareDefaultValue(rs.getString("COLUMN_DEF")); 
			} catch (Exception e) {
				defa = "";
			}
			if (defa != null) setValue(defa);			
			
			autoincrement = dialect.isIdentityColumn(rs.getString(dialect.getIdentityParseColumnName()));
			/*
			System.out.println(" = "+rs.getString("TABLE_CAT"));
			System.out.println(" = "+rs.getString("TABLE_SCHEM"));
			System.out.println(" = "+rs.getString("TABLE_NAME"));
			System.out.println("COLUMN_NAME = "+rs.getString("COLUMN_NAME"));
			System.out.println("DATA_TYPE = "+rs.getString("DATA_TYPE"));
			System.out.println("TYPE_NAME = "+rs.getString("TYPE_NAME"));
			System.out.println("COLUMN_SIZE = "+rs.getString("COLUMN_SIZE"));
			System.out.println("BUFFER_LENGTH = "+rs.getString("BUFFER_LENGTH"));
			System.out.println("DECIMAL_DIGITS = "+rs.getString("DECIMAL_DIGITS"));
			System.out.println("NUM_PREC_RADIX = "+rs.getString("NUM_PREC_RADIX"));
			System.out.println("NULLABLE = "+rs.getString("NULLABLE"));
			System.out.println("REMARKS = "+rs.getString("REMARKS"));
			System.out.println("COLUMN_DEF = "+rs.getString("COLUMN_DEF"));
			System.out.println("SQL_DATA_TYPE = "+rs.getString("SQL_DATA_TYPE"));
			System.out.println("SQL_DATETIME_SUB = "+rs.getString("SQL_DATETIME_SUB"));
			System.out.println("CHAR_OCTET_LENGTH = "+rs.getString("CHAR_OCTET_LENGTH"));
			System.out.println("ORDINAL_POSITION = "+rs.getString("ORDINAL_POSITION"));
			System.out.println("IS_NULLABLE = "+rs.getString("IS_NULLABLE"));
			System.out.println("-------------------------------------------");
			*/
		} catch (Exception exc) {
			//exc.printStackTrace();
			throw exc;
		}
	}

	/**
	 * Transform - transformuje MetaData na fragment SQL DDL.
	 * 
	 * @return String
	 */
	public String transform() throws Exception {
		//ArrayList<String> identities = new ArrayList<String>();
		StringBuffer sb = new StringBuffer("");
		if (prefix) sb.append(this.getDialect().LQ() + getTable() + this.getDialect().RQ() + ".");
		
		sb.append((getDialect().isKeyword(getName())) ? 
		    (this.getDialect().LQ() + getName() + "_" + this.getDialect().RQ()) : 
		    (this.getDialect().LQ() + getName() + this.getDialect().RQ())
		);
		this.setTypeName(getDialect().getColumnTypeString(
		    this.getSqlType(), this.getSize(), this.getDecimal())
		);
		sb.append(" " + getTypeName() + " ");
		if (this.autoincrement) {
			sb.append(" " + dialect.getIdentityBuildPart() + " ");
		}
		if (dialect.isColumnConstrainted()) {
			sb.append("CONSTRAINT ["+dialect.getColumnConstraintKey(this.getTable(), getName())+"] ");
		}
		int type = this.getSqlType();
		
		if (this.autoincrement) {
			sb.append(" NOT NULL ");
		} else { 
			if (isNullable()) {
				if (this.getDialect().isAppendDefaultIfNull()) {
					if (this.hasValue()) {
						sb.append("DEFAULT " + value + " ");
					}				
					sb.append(getDialect().getNullColumnString());
				} else {
					if (type == -1) {
						sb.append(" NOT NULL ");
					} else {
						if (this.hasValue()) {
							sb.append("DEFAULT " + value + " ");
						}
						sb.append(" NOT NULL ");
					}
				}
			} else { 
				if (type == -1) {
					sb.append(" NOT NULL ");
				} else {
					sb.append("DEFAULT " + value + " ");
					sb.append(" NOT NULL ");
				}
			}
		}
		if (isPK()) {
			sb.append(" PRIMARY KEY");
		}
		//System.out.println("FieldMetaData.transform()");
		return sb.toString();		
	}
	
	// -----  -----------------------------------------------------------------

	/**
	 * Funkce p�etransformuje objekt typu FieldMetaData do XML
	 * @param 
	 * @return String - n�vratov� hodnota je �et�zec XML
	 */
	public String transToXML() throws Exception {
		DOMElement node = new DOMElement("FMD");
		return transToXML(node).asXML();
	}

	/**
	 * Funkce p�etransformuje objekt typu FieldMetaData do objekt typu DOMElement 
	 * @param DOMElement node 
	 * @return DOMElement
	 */
	public DOMElement transToXML(DOMElement node) throws Exception {
		//DOMElement child = new DOMElement("FMD");
		node.setName("FMD");
		try {
			node.setAttribute("table", (table != null) ? table : "");
			node.setAttribute("name", (name != null) ? name : "");
			node.setAttribute("type", (type != null) ? type : "");
			node.setAttribute("value", (value != null) ? value : "");

			node.setAttribute("sqltype", "" + sqltype);
			node.setAttribute("size", "" + size);
			node.setAttribute("decimal", "" + decimal);
			node.setAttribute("isnull", "" + nullable);
			node.setAttribute("prefix", "" + prefix);	
		} catch (Exception exc) {
			throw exc;
		}
		return node;
	}
	
	/**
	 * Funkce p�etransformuje �et�zec XML na objekt typu FieldMetaData 
	 * @param String xml 
	 * @return FieldMetaData
	 */
	public FieldMetaData xmlToExpr(String xml) throws Exception {
		Document doc = DocumentHelper.parseText(xml);
		Element root = doc.getRootElement();
		return this.xmlToExpr(root);
	}

	/**
	 * Funkce p�etransformuje objekt typu org.dom4j.Element na objekt typu FieldMetaData 
	 * @param Element node
	 * @return FieldMetaData
	 */	
	public FieldMetaData xmlToExpr(Element node) throws Exception {
		//System.out.println("UniQLExpr.xmlToExpr( START )");
		if (node.getName().equals("FMD")) {
			this.setName(node.attributeValue("name"));
			this.setTable(node.attributeValue("table"));
			this.setTypeName(node.attributeValue("type"));

			this.setSqlType(node.attributeValue("sqltype"));			
			this.setSize(node.attributeValue("size"));
			this.setDecimal(node.attributeValue("decimal"));
			this.setIsNull(node.attributeValue("isnull"));
			this.setPrefix(node.attributeValue("prefix"));
						
			String value = node.attributeValue("value");
			if (value.startsWith("'") && value.endsWith("'")) {
				value = value.substring(1, value.length()-1);
				if (value != null && value.toUpperCase().equals("NULL")) {
					value = "";
				}
			}
			//System.out.println("value = "+value); 
			this.setValue(value);

		}
		//System.out.println("UniQLExpr.xmlToExpr( END )");
		return this;
	}
	
	// -----  -----------------------------------------------------------------
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return this.getDialect().getCreateTableFieldString(this);		
	}

	// ----------------------- -------------------------------------------------

	public void set(String table, String name, int sqlType, int size, int decimal, boolean nullable, String value, boolean pk, boolean unique, boolean autoincrement) throws Exception {
		setTable(table);
		setName(name);
		setSqlType(sqlType);
		setSize(size);
		setDecimal(decimal);
		setNullable(nullable);
		setValue(value);
		setPK(pk);
		setUnique(unique);
		setAutoincrement(autoincrement);
	}

	/**
	 * Navraci jmeno tabulky.
	 * 
	 * @return String
	 */
	public String getTable() {
		return table;
	}

	/**
	 * Zadava jmeno tabulky.
	 * 
	 * @param table - jmeno tabulky
	 */
	public void setTable(String table) {
		this.table = table;
	}

	/**
	 * Navraci jmeno sloupce.
	 * 
	 * @return String
	 */
	public String getName() {
		return name;
	}

	/**
	 * Zadava jmeno sloupce.
	 * 
	 * @param name - name
	 */
	public void setName(String name) {
		this.name = name;
	}

	public String getQuotedName() {
		return dialect.LQ() + name + dialect.RQ();
	}

	public String getQuotedName(Dialect d) {
		return d.LQ() + name + d.RQ();
	}

	// ----->  <-----
	
	/**
	 * Navraci SQL typ sloupce (viz java.sql.Types).
	 * 
	 * @return int
	 */
	public int getSqlType() {
		return sqltype;
	}

	/**
	 * Zadava hodnotu promenne SQL type.
	 * 
	 * @param sqltype - sql type
	 */
	public void setSqlType(int sqltype) {
		this.sqltype = sqltype;
	}

	/**
	 * Zadava hodnotu promenne SQL type.
	 * 
	 * @param sqltype - sql type
	 */
	public void setSqlType(String sqltype) {
		if (sqltype != null) {
			this.sqltype = Integer.valueOf(sqltype).intValue();
		}
	}

	/**
	 * Navraci hodnotu promenn� type name.
	 * 
	 * @return type name
	 */
	public String getTypeName() {
		return type;
	}

	/**
	 * Zadava hodnotu promenne type name.
	 * 
	 * @param type - type name
	 */
	public void setTypeName(String type) {
		this.type = type;
	}

	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	/*
	public void setIsNull(boolean isnull) {
		this.isnull = isnull;
	}
	*/
	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	/*
	public void setIsNull(String isnull) {
		if (isnull != null) {
			this.isnull = isnull.toUpperCase().equals("TRUE");
		} else {
			this.isnull = false;
		}
	}
	*/
	/**
	 * Navraci true pokud je sloupec NULL a false pokud je NOT NULL.
	 * 
	 * @return boolean
	 *
	 */
	/*
	public boolean isNull() {
		return isnull;
	}
	*/
	/**
	 * Navraci delku sloupce.
	 * 
	 * @return int
	 */
	public int getSize() {
		return size;
	}

	/**
	 * Zadava delku sloupce.
	 * 
	 * @param size - delka sloupce
	 */
	public void setSize(int size) {
		this.size = size;
	}

	/**
	 * Zadava delku sloupce.
	 * 
	 * @param size - delka sloupce
	 */
	public void setSize(String size) {
		if (size != null) {
			this.size = Integer.valueOf(size).intValue();
		}
	}
	/*
	public int getLength() {
		return length;
	}
	
	public void setLength(int length) {
		this.length = length;
	}
	*/
	/**
	 * Navraci delku decimalni ��sti u numerick�ch sloupcu.
	 * 
	 * @return int
	 */
	public int getDecimal() {
		return decimal;
	}

	/**
	 * Zadava delku decimalni ��sti u numerick�ch sloupcu.
	 * 
	 * @param decimal - delka decimalni ��sti. 
	 */
	public void setDecimal(int decimal) {
		this.decimal = decimal;
	}

	/**
	 * Zadava delku decimalni ��sti u numerick�ch sloupcu.
	 * 
	 * @param decimal - delka decimalni ��sti. 
	 */
	public void setDecimal(String decimal) {
		if (decimal != null) {
			this.decimal = Integer.valueOf(decimal).intValue();	
		}		
	}

	// -----> precision <-----
	/*
	public int getPrecision() {
		return precision;
	}
	
	public void setPrecision(int scale) {
		this.precision = scale;
	}
	// -----> scale <-----
	
	public int getScale() {
		return scale;
	}
	
	public void setScale(int scale) {
		this.scale = scale;
	}
	*/
	// ----->  <-----
	
	/**
	 * Navraci true pokud je sloupec NULL a false pokud je NOT NULL.
	 * 
	 * @return boolean
	 */
	@Deprecated
	public boolean isNull() {
		return isNullable();
	}

	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	@Deprecated
	public void setIsNull(boolean nullable) {
		this.setNullable(nullable);
	}

	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	@Deprecated
	public void setIsNull(String nullable) {
		this.setNullable(nullable);
	}

	/**
	 * Navraci true pokud je sloupec NULL a false pokud je NOT NULL.
	 * 
	 * @return boolean
	 */
	public boolean isNullable() {
		return nullable;
	}

	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	public void setNullable(boolean nullable) {
		this.nullable = nullable;
	}
	
	/**
	 * Zadava hodnotu promenne NULL/NOT NULL.
	 * 
	 * @param isnull - isnull
	 */
	public void setNullable(String nullable) {
		if (nullable != null) {
			this.nullable = nullable.toUpperCase().equals("TRUE");
		} else {
			this.nullable = false;
		}
	}

	// -----> <-----
	/**
	 * Navrac� true pokud m� sloupec zad�nu defaultn� hodnotu.
	 * 
	 * @return boolean
	 */
	public boolean hasValue() {
		if (value == null || value.length() == 0) return false;
		return true;
	}
	
	/**
	 * Navraci defaultn� hodnotu sloupce.
	 * 
	 * @return String
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Zadava defaultn� hodnotu sloupce.
	 * 
	 * @param value - defaultn� hodnotu sloupce.
	 */
	public void setValue(String value) throws Exception {
		this.setDefaultValue(value);
		switch (getSqlType()) {
			case Types.TIMESTAMP:
				if (value == null || !value.equals("1900-01-01 00:00:01"))
					value = "1900-01-01 00:00:01";
				value = this.dialect.getValueTimestamp(value);
				break;
			case Types.TIME:
				if (value == null || !value.equals("00:00:00"))
					value = "00:00:01";
				value = this.dialect.getValueTime(value);
				break;
			case Types.DATE:
				if (value == null || !value.equals("1900-01-01"))
					value = "1900-01-01";
				value = this.dialect.getValueDate(value);
				break;
			case Types.LONGVARCHAR:
			case Types.VARBINARY:
			case Types.BLOB:
			case Types.CLOB:
				value = this.dialect.getValueClob(value);
				break;
			case Types.DECIMAL:
			case Types.DOUBLE:
			case Types.FLOAT:
				if (value == null || value.equals(""))
					value = "0.00";
				//value = "'"+value+"'";
				value = this.dialect.getValueDouble(value);
				break;
			case Types.TINYINT:
			case Types.INTEGER:
			case Types.BIGINT:
				if (value == null || value.equals(""))
					value = "0";
				value = this.dialect.getValueInteger(value); 
				break;
			default: { 
				int len = 4096;
				if (this.getSize() > 0) len = this.getSize();  
				value = this.dialect.getValueString(value, len);
			}
		}
		this.value = value;			
	}

	/**
	 * 
	 * @return
	 */
	public String getDefaultValue() {
		return defaultValue;
	}

	/**
	 * 
	 * @param defaultValue
	 */
	private void setDefaultValue(String defaultValue) {
		this.defaultValue = defaultValue;
	}

	// -----  -----------------------------------------------------------------
	
	/**
	 * 
	 */
	public boolean isPK() {
		return this.pk;
	}

	/**
	 * 
	 * @param autoincrement
	 */
	public void setPK(boolean pk) {
		this.pk = pk;
	}

	// -----> unique <-----

	public boolean isUnique() {
		return unique;
	}

	public void setUnique(boolean unique) {
		this.unique = unique;
	}

	//-----> aupeincrement <-----
	
	/**
	 * 
	 */
	public boolean isAutoincrement() {
		return autoincrement;
	}

	/**
	 * 
	 * @param autoincrement
	 */
	public void setAutoincrement(boolean autoincrement) {
		this.autoincrement = autoincrement;
	}

	// -----  -----------------------------------------------------------------
	
	/**
	 * Navraci dialect.
	 * 
	 * @return dialect
	 */
	public Dialect getDialect() {
		return this.dialect;
	}

	/**
	 * Zadava dialect.
	 * 
	 * @param dialect - dialect
	 */
	public void setDialect(Dialect dialect) {
		this.dialect = dialect;
	}

	// -----  -----------------------------------------------------------------
	
	/**
	 * @return the dbmd
	 */
	public DBMetaData dbmd() {
		return dbmd;
	}

	/**
	 * @return the dbmd
	 */
	public DBMetaData getDBMetaData() {
		return dbmd;
	}

	/**
	 * @param dbmd the dbmd to set
	 */
	public void setDBMetaData(DBMetaData dbmd) {
		this.dbmd = dbmd;
	}

	/**
	 * @return the tbmd
	 */
	public TableMetaData tbmd() {
		return tbmd;
	}

	/**
	 * @return the tbmd
	 */
	public TableMetaData getTableMetaData() {
		return tbmd;
	}

	/**
	 * @param tbmd the tbmd to set
	 */
	public void setTableMetaData(TableMetaData tbmd) {
		this.tbmd = tbmd;
	}

	/**
	 * Checks if is prefix.
	 * 
	 * @return true, if is prefix
	 */
	public boolean isPrefix() {
		return prefix;
	}

	/**
	 * Zadava hodnotu promenne prefix.
	 * 
	 * @param prefix - prefix
	 */
	public void setPrefix(boolean prefix) {
		this.prefix = prefix;
	}
	
	/**
	 * Zadava hodnotu promenne prefix.
	 * 
	 * @param prefix - prefix
	 */
	public void setPrefix(String prefix) {
		if (prefix != null) {
			this.prefix = prefix.toUpperCase().equals("TRUE");
		} else {
			this.prefix = false;		
		}
	}

	// -----  -----------------------------------------------------------------
	
	public boolean isQuoted() {
		return quoted;
	}

	// -----  -----------------------------------------------------------------
	
	public boolean equals(Object object) {
		return object instanceof FieldMetaData && equals((FieldMetaData)object);
	}

	public boolean equals(FieldMetaData fmd) {
		if (null == fmd) return false;
		if (this == fmd) return true;
		return name.equalsIgnoreCase(fmd.name);
	}
		
	public boolean compare(FieldMetaData fmd) {
		if (null == fmd) return false;
		if (this == fmd) return true;
		if (!name.equalsIgnoreCase(fmd.name)) return false;
		if (sqltype != fmd.sqltype) return false;
		switch (sqltype) {
			case Types.DOUBLE:
			case Types.FLOAT: {
				if (size > 38) size = 38;
				if (size < 15) size = 15;
				if (size <= decimal) { size = decimal; decimal = decimal - 1; }
				break;
			}
		}
		switch (sqltype) {
			case Types.BLOB:
			case Types.CLOB:
			case Types.LONGVARBINARY:
			case Types.LONGVARCHAR: {
				break;
			}
			case Types.TIMESTAMP:
			case Types.DATE: {
				break;
			}
			default: {				
				if (this.size != fmd.size) return false;
				if (this.decimal != fmd.decimal) return false;
			}
		}
		if (!new Boolean(this.nullable).equals(fmd.nullable)) return false;
		switch (sqltype) {
			case Types.DOUBLE:
			case Types.FLOAT: {
				if (!this.nullable) {
					double d1 = parseDouble(defaultValue);
					double d2 =	parseDouble(fmd.defaultValue);
					if (d1 != d2) return false; 
				}
				break;
			}
			default: {
				if (!defaultValue.equalsIgnoreCase(fmd.defaultValue)) return false;
			}
		}		
		//defaultValue;
		//private String table = null;
		//value;
		return name.equalsIgnoreCase(fmd.name);
	}
	
	private double parseDouble(String s) {
		try {
			s = decimalFormat(s);
			double d = Double.parseDouble(s);
			return d;			
		} catch (Exception e) {
			return 0.0;
		}
	}

	private String decimalFormat(String s) throws Exception {
		String s1 = "";
		boolean decimal = true;
		s = s.trim();
		int i = s.length();
		while (i > 0) {
			char c = s.charAt(i-1);
			if (c == '-') {
				if (i > 1) c = '0';	
			} else if (c == ',') {
				if (decimal) {
					decimal = false;
					if (i > 0) c = '.';
				} else {
					c = 0xFF;
				}
			} else if (c == '.') {
				if (decimal) {
					decimal = false;
				} else {
					c = 0xFF;
				}					
			} else if (!Character.isDigit(c)) {
				c = 0xFF;
			}
			if (c < 64) s1 = c + s1;
			i--; 
		}
		return s1;
	}

	public int hashCode() {
		return name.toLowerCase().hashCode();
	}
}
